import base64
import json
import random
import re
import time

import cv2
import numpy as np
import parsel
import requests
import rsa
from loguru import logger

from apps.pdd.pdd.mms import gen_anti_content
from apps.pdd.pdd.utils.js import get_prepare_token, deal_encrypt_salt, img_decode
from utils.proxies_tools import get_company_ip_crawler_by_api


class MmsVerify:
    session = requests.Session()
    code_msg = {
        "1000": "验证时间过长，请重试",
        "1001": "验证时间过长，请重试",
        "1002": "系统繁忙，请稍后重试",
        "1004": "验证频繁，请稍后重试",
        "3000": "系统繁忙，请稍后重试",
        "4000": "网络异常，请重试",
        "": "网络异常",
        "0": "调用成功",
    }

    def __init__(self, verify_auth_token=None):
        self.base_url = "https://apiv2.pinduoduo.net"
        self.random_aes_iv = None
        self.random_aes_key = None
        self.aes_iv = None
        self.aes_key = None
        self.verify_auth_token = verify_auth_token

    def init(self):
        self.session.proxies = get_company_ip_crawler_by_api()
        logger.debug(f"设置代理 {self.session.proxies}")
        self.session.headers = {
            "User-Agent": f"Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/{random.randint(600, 700)}.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1",
        }
        url = f"{self.base_url}/psnl_verification.html?VerifyAuthToken={self.verify_auth_token}"
        logger.info(f"请求验证码页面: {url}")
        html = self.session.get(url, timeout=5).text
        js_url = parsel.Selector(html).xpath('//script[contains(@src, "psnl_verification")]/@src').get()
        js_url = (
            js_url
            or parsel.Selector(html)
            .xpath('//link[contains(@href, "psnl_verification") and contains(@href, ".js")]/@href')
            .get()
        )
        logger.debug(f"请求验证码js: {js_url}")
        js_code = self.session.get(js_url, timeout=5).text
        key_iv = re.findall(r"=\"([^\"]*?)\",\w=\"([^\"]*?)\".{1,200}{a:\w\(16\),v:\w\(16\)}", js_code)
        if key_iv:
            self.aes_key, self.aes_iv = key_iv[0]
        else:
            raise Exception("获取aes_key,aes_iv失败")

        self.random_aes_key = self.gen_random_str(16)
        self.random_aes_iv = self.gen_random_str(16)

    def encrypt_b64(self, text=None):
        public_key = "00B5F32E7F62AC544E16C185366CC4D743A28C6D23D94648862530A1A984090ED6B59C6C8F04C4874245F26C9693C8085AE5E36A411D29D769C2210FFEDFE330254812D88C559E9618AE945FD4AD197FE8E4C148B439F3C277E78B38B90058759C8EAC3E67C7A6EB4B3B76650971BCA6B6E41FC74437A4012ECE410D8B0AD194C1B5CDD8BED891670274A4F88AAA4438361A384A5CA89EA6DBAB27E7934A5B82EFD5854D114803195572EAC50908C9335A523B81AD0C5AFA8E50C626B403965F2622CF38BDD2246A807B552D7344D6EE1A6E30A4E3B3A4A94C6CA76C3A96782639521FF91D3C0C076150F117DEE1E36E0A35E8A50AC1A465686DAD1AB7078592C1"
        public_key_mode = "010001"
        bytes_data = rsa.encrypt(text.encode("utf-8"), rsa.PublicKey(int(public_key, 16), int(public_key_mode, 16)))
        new_text = base64.b64encode(bytes_data).decode("utf-8")
        return new_text

    @staticmethod
    def gen_random_str(bit=16):
        base_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
        return "".join(random.choices(base_str, k=bit))

    def get_salt(self):
        url = f"{self.base_url}/api/phantom/vc_pre_ck_b?pdduid=0"
        anti_content = gen_anti_content()

        data = {
            "anti_content": anti_content,
            "sdk_type": 3,
            "client_time": int(time.time() * 1000),
        }
        data = json.dumps(data, separators=(",", ":"))
        headers = {
            **self.session.headers,
            "Content-Type": "application/json;charset=UTF-8",
        }
        response = self.session.post(url, data=data, headers=headers).json()
        salt = response.get("salt")
        servertime = response["server_time"]
        return salt, servertime

    def get_image(self, servertime, salt, salt_aes_key, salt_aes_iv):
        url = f"{self.base_url}/api/phantom/obtain_captcha"
        data = {
            "anti_content": gen_anti_content(),
            "captcha_collect": get_prepare_token(
                {
                    "v": "b",
                    "ts": servertime,
                    "t0": int(time.time() * 1000),
                    "tp": 1,
                    "ua": self.session.headers["User-Agent"],
                    "rf": f"{self.base_url}",
                    "hl": "000000001001010",
                    "sc": {"w": 400, "h": 700},
                    "ihs": 1,
                    "platform": 3,
                },
                key=salt_aes_key,
                iv=salt_aes_iv,
            ),
            "verify_auth_token": self.verify_auth_token,
        }
        resp = self.session.post(url, json=data).json()
        resp.update({"msg": self.code_msg.get(str(str(resp["code"])))})
        return resp

    def gen_track(self, distance):
        # 不校验
        def __ease_out_expo(sep):
            """
            轨迹相关操作
            """
            if sep == 1:
                return 1
            else:
                return 1 - pow(2, -10 * sep)

        if not isinstance(distance, int) or distance < 0:
            raise ValueError(f"distance类型必须是大于等于0的整数: distance: {distance}, type: {type(distance)}")
        # 初始化轨迹列表
        slide_track = []
        # 共记录count次滑块位置信息
        count = 20
        # 初始化滑动时间
        t = int(time.time() * 1000) - 4000
        # 记录上一次滑动的距离
        _x = 0
        _y = 0
        for i in range(count):
            # 已滑动的横向距离
            x = round(__ease_out_expo(i / count) * distance)
            # y = round(__ease_out_expo(i / count) * 14)
            # 滑动过程消耗的时间
            t += random.randint(10, 50)
            if x == _x:
                continue
            slide_track.append([x, random.randint(170, 180) + 0.5, t, 11.5, 11.5, 0, 1])
            _x = x
        slide_track.append(slide_track[-1])
        return slide_track

    def verify_image(self, distance, servertime, salt, salt_aes_key, salt_aes_iv):
        url = f"{self.base_url}/proxy/api/api/phantom/user_verify?pdduid=0"
        track = self.gen_track(distance)
        data = {
            "anti_content": gen_anti_content(),
            "verify_auth_token": self.verify_auth_token,
            "captcha_collect": get_prepare_token(
                {
                    "v": "b",
                    "ts": servertime,
                    "t1": int(time.time() * 1000),
                    "t2": track[-1][2],
                    "tp": 1,
                    "ua": self.session.headers["User-Agent"],
                    "rf": f"{self.base_url}",
                    "platform": 3,
                    "hl": "000000001001010",
                    "sc": {"w": 400, "h": 700},
                    "ihs": 1,
                    "imageSize": {"width": 267, "height": 133},
                    "del": track[:1],
                    "mel": track,
                    "uel": track[-1:],
                    "mell": [],
                    "de": [
                        [{"class": "QkGS9nId", "id": None}],
                        [{"class": "QkGS9nId", "id": None}],
                        [{"class": "QkGS9nId", "id": None}],
                        [{"class": "slide-ani", "id": None}],
                    ],
                    "isb": 1,
                },
                key=salt_aes_key,
                iv=salt_aes_iv,
            ),
            "verify_code": int(distance),
        }
        ret = self.session.post(url, json=data).json()
        msg = f"验证结果：{ret}"
        if ret.get("code") == 0:
            if ret.get("result") is True:
                logger.success(msg)
                return True
            else:
                logger.error(msg)
                return False
        else:
            logger.error(msg)
            return False

    def get_image_distance(self, target_bytes, background_bytes):
        target = cv2.imdecode(np.frombuffer(target_bytes, np.uint8), cv2.IMREAD_ANYCOLOR)
        background = cv2.imdecode(np.frombuffer(background_bytes, np.uint8), cv2.IMREAD_ANYCOLOR)
        background = cv2.GaussianBlur(background, (5, 5), 1)
        _, background = cv2.threshold(background, 110, 255, cv2.THRESH_BINARY)
        _, target = cv2.threshold(target, 20, 255, cv2.THRESH_BINARY)
        target = cv2.bitwise_not(target)
        res = cv2.matchTemplate(background, target, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        return int(max_loc[0])

    def run_one(self):
        logger.info(f"{'*' * 50} PDD 验证开始 {'*' * 50}")
        self.init()
        salt, servertime = self.get_salt()
        if not salt:
            raise Exception(f"{self.verify_auth_token} 已过期")
        salt_aes = deal_encrypt_salt(salt, self.aes_key, self.aes_iv)
        salt_aes_key = salt_aes["aes_key"]
        salt_aes_iv = salt_aes["aes_iv"]
        logger.debug(f"salt: {salt}, servertime: {servertime} salt_aes_key: {salt_aes_key}, salt_aes_iv: {salt_aes_iv}")
        for _ in range(3):
            image_info = self.get_image(servertime, salt, salt_aes_key, salt_aes_iv)
            logger.info(f"verify_type: {image_info.get('type')} msg: {image_info.get('msg')}")
            if image_info.get("msg") == "验证频繁，请稍后重试":
                return False
            if str(image_info.get("type")) == "23":
                print("历史商品选择")
                continue
            pictures = image_info.get("pictures")
            bg1 = base64.b64decode(img_decode(pictures[0]).replace("data:image/png;base64,", ""))
            bg2 = base64.b64decode(img_decode(pictures[1]).replace("data:image/png;base64,", ""))
            distance = self.get_image_distance(target_bytes=bg2, background_bytes=bg1)
            new_distance = int(distance * 267 / 320) + int((60 * (267 / 320)) / 2)
            logger.debug(f"distance: before: {distance}, after: {new_distance}")
            verify_result = self.verify_image(new_distance, servertime, salt, salt_aes_key, salt_aes_iv)
            if verify_result is False:
                continue
            logger.info(f"{'*' * 50} PDD 验证结束 {'*' * 50}")
            return verify_result

        raise Exception("滑块验证失败")

    def run(self):
        for i in range(3):
            try:
                return self.run_one()
            except Exception as e:
                logger.error(e)
                continue
        return False


if __name__ == "__main__":
    test = MmsVerify("POzAFRK-xg33KqCN5yOSkgc88b111bf667310e2")
    test.run()
